package com.bugsnag.ohos;

import com.bugsnag.ohos.internal.ImmutableConfig;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicReference;

/**
 * This class is responsible for persisting and retrieving user information.
 *
 * @since 2021-07-05
 */
public class UserStore {
    File file;
    private ImmutableConfig config;
    private String deviceId;
    private SharedPrefMigrator sharedPrefMigrator;
    private Logger logger;
    private SynchronizedStreamableStore<User> synchronizedStreamableStore;
    private boolean persist;
    private AtomicReference previousUser = new AtomicReference<User>(null);

    /**
     * constructor
     *
     * @param config
     * @param deviceId
     * @param sharedPrefMigrator
     * @param logger
     */
    public UserStore(ImmutableConfig config, String deviceId, SharedPrefMigrator sharedPrefMigrator, Logger logger) {
        this(config, deviceId, new File(config.getPersistenceDirectory(), "user-info"), sharedPrefMigrator, logger);
    }

    /**
     * constructor
     *
     * @param config
     * @param deviceId
     * @param file
     * @param sharedPrefMigrator
     * @param logger
     */
    public UserStore(ImmutableConfig config, String deviceId, File file,
                     SharedPrefMigrator sharedPrefMigrator, Logger logger) {
        this.config = config;
        this.deviceId = deviceId;
        this.file = file;
        this.sharedPrefMigrator = sharedPrefMigrator;
        this.logger = logger;
        persist = config.getPersistUser();
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (IOException exc) {
            logger.w("Failed to created device ID file", exc);
        }
        this.synchronizedStreamableStore = new SynchronizedStreamableStore(file);
    }

    /**
     * Loads the user state which should be used by the [Client]. This is supplied either from
     * the [Configuration] value, or a file in the [Configuration.getPersistenceDirectory] if
     * [Configuration.getPersistUser] is true.
     * <p>
     * If no user is stored on disk, then a default [User] is used which uses the device ID
     * as its ID.
     * <p>
     * The [UserState] provides a mechanism for observing value changes to its user property,
     * so to avoid interfering with this the method should only be called once for each [Client].
     *
     * @param initialUser
     * @return User state
     */
    public UserState load(User initialUser) {
        boolean validConfigUser = validUser(initialUser);
        User loadedUser;
        if (validConfigUser) {
            loadedUser = initialUser;
        } else if (persist) {
            loadedUser = loadPersistedUser();
        } else {
            loadedUser = null;
        }
        UserState userState;
        if (loadedUser != null && validUser(loadedUser)) {
            userState = new UserState(loadedUser);
        } else {
            userState = new UserState(new User(deviceId, null, null));
        }
        userState.addObserver(
            event -> {
                if (event instanceof StateEvent.UpdateUser) {
                    save(((StateEvent.UpdateUser) event).getUser());
                }
            }
        );
        return userState;
    }

    /**
     * Persists the user if [Configuration.getPersistUser] is true and the object is different
     * from the previously persisted value.
     */
    void save(User user) {
        if (persist && user != previousUser.getAndSet(user)) {
            try {
                synchronizedStreamableStore.persist(user);
            } catch (IOException exc) {
                logger.w("Failed to persist user info", exc);
            }
        }
    }

    private boolean validUser(User user) {
        return user.getId() != null || user.getName() != null || user.getEmail() != null;
    }

    private User loadPersistedUser() {
        if (sharedPrefMigrator.hasPrefs()) {
            User legacyUser = sharedPrefMigrator.loadUser(deviceId);
            save(legacyUser);
            return legacyUser;
        } else {
            try {
                return synchronizedStreamableStore.load(jsonReader -> new User().fromReader(jsonReader));
            } catch (FileNotFoundException | UnsupportedEncodingException exc) {
                logger.w("Failed to load user info", exc);
                return null;
            }
        }
    }
}
